<?php

class OOWP_PyList
    extends OOWP_Base
    implements ArrayAccess, Countable, Iterator
{

    protected $container = array( );

    public function __construct(array $classes = array( ))
    {
        if (OOWP_Base::is_list_of_wellformed_strings($classes)) {
            $this->dict['classes'] = $classes;
        } else {
            throw new Exception('Bad class list provided.');
        }
    } // __construct

    /* ACCESSORS */

    protected function getClasses( )
    {
        return $this->dict['classes'];
    } // getClasses

    /* PY-STYLE ACCESSORS */

    public function append($item)
    {
        if ($this->validate_input($item)) {
            $this->container[ ] = $item;
        } else {
            throw new Exception('Invalid input.');
        }
    } // append

    public function extend(array $items)
    {
        foreach ($items as $item) {
            if (!$this->validate_input($item)) {
                throw new Exception('Invalid input: ' . $item);
            }
        }
        foreach ($items as $item) {
            $this->container[ ] = $item;
        }
    } // extend

    public function pop($index)
    {
        if (!is_integer($index)) {
            throw new Exception('Expected integer index.');
        }
        $new_index = $this->rewrite_index($index);
        if (($new_index < 0) || (count($this) <= $new_index)) {
            throw new Exception('Index out of bounds.');
        }
        $result = array_splice($this->container, $new_index, 1);
        return $result[0];
    } // pop

    public function insert($index, $item)
    {
        if (!$this->validate_input($item)) {
            throw new Exception('Invalid input.');
        }
        if (!is_integer($index)) {
            throw new Exception('Expected integer index.');
        }
        $new_index = $this->rewrite_index($index);
        if ($new_index < 0) {
            $new_index = 0;
        } elseif (count($this) <= $new_index) {
            $new_index = count($this) - 1;
        }
        $left = array_slice($this->container, 0, $new_index);
        $right = array_slice($this->container, $new_index);
        $this->container = array_merge($left, array($item), $right);
    } // insert

    protected function rewrite_index($index)
    {
        if ($index < 0) {
            return $index + count($this);
        }
        return $index;
    } // rewrite_index

    /* ARRAY ACCESS */

    public function offsetSet($index, $item)
    {
        if (!$this->validate_input($item)) {
            throw new Exception('Invalid input.');
        }
        if (is_null($index)) {
            $this->container[ ] = $item;
        } else {
            $new_index = $this->rewrite_index($index);
            $this->container[$new_index] = $item;
        }
    } // offsetSet

    public function offsetExists($index)
    {
        $new_index = $this->rewrite_index($index);
        return isset($this->container[$new_index]);
    } // offsetExists

    public function offsetUnset($index)
    {
        $new_index = $this->rewrite_index($index);
        unset($this->container[$new_index]);
    } // offsetUnset

    public function offsetGet($index)
    {
        $new_index = $this->rewrite_index($index);
        if (isset($this->container[$new_index])) {
            return $this->container[$new_index];
        }
        return null;
    } // offsetGet

    /* COUNTABLE */

    public function count( )
    {
        return count($this->container);
    } // count

    /* ITERATOR */

    function rewind( )
    {
        return reset($this->container);
    } // rewind

    function current( )
    {
        return current($this->container);
    } // current

    function key( )
    {
        return key($this->container);
    } // key

    function next( )
    {
        return next($this->container);
    } // next

    function valid( )
    {
        return key($this->container) !== null;
    } // valid

    /* MISC */

    public function toArray( )
    {
        $result = array( );
        foreach ($this->container as $item) {
            $result[ ] = $item;
        }
        return $result;
    } // toArray

    protected function validate_input($item)
    {
        if (count($this->classes)) {
            if (gettype($item) == 'object') {
                foreach ($this->classes as $parent) {
                    if (is_subclass_of($item, $parent)) {
                        return true;
                    }
                }
            } elseif (in_array(gettype($item), $this->classes)) {
                return true;
            } elseif (is_null($item) && in_array('null', $this->classes)) {
                return true;
            }
            return false;
        }
        return true;
    } // validate_input

}
